package cn.iocoder.yudao.module.trade.scan.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.business.basic.dao.query.CoreQueryWrapper;
import cn.iocoder.yudao.framework.business.basic.dao.repository.SerialNoRedisRepository;
import cn.iocoder.yudao.framework.business.basic.pojo.vo.UserSimpleVO;
import cn.iocoder.yudao.framework.business.order.pojo.vo.OrderSnapshotVO;
import cn.iocoder.yudao.framework.business.order.pojo.vo.OrderSubmitVO;
import cn.iocoder.yudao.framework.business.order.service.AppOrderBaseService;
import cn.iocoder.yudao.framework.common.exception.Assertions;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.api.infrastructure.config.ConfigApiV2;
import cn.iocoder.yudao.module.api.infrastructure.config.dto.MemberConfigRespDTO;
import cn.iocoder.yudao.module.api.member.assets.MemberDigitalAssetsApi;
import cn.iocoder.yudao.module.api.member.assets.dto.MemberDigitalAssetsRespDTO;
import cn.iocoder.yudao.module.api.member.assets.enums.CryptocurrencyDeductionType;
import cn.iocoder.yudao.module.api.member.point.MemberPointApi;
import cn.iocoder.yudao.module.api.member.point.enums.MemberPointBizTypeEnum;
import cn.iocoder.yudao.module.api.member.user.MemberUserApi;
import cn.iocoder.yudao.module.api.member.vip.VipUserApi;
import cn.iocoder.yudao.module.api.pay.bill.PayBillApi;
import cn.iocoder.yudao.module.api.pay.bill.dto.PayBillCreateDTO;
import cn.iocoder.yudao.module.api.pay.bill.enums.BillCategoryEnum;
import cn.iocoder.yudao.module.api.pay.bill.enums.BillTypeEnum;
import cn.iocoder.yudao.module.api.pay.order.PayOrderApi;
import cn.iocoder.yudao.module.api.pay.order.dto.PayOrderCreateDTO;
import cn.iocoder.yudao.module.api.promotion.activity.enums.PromotionDiscountTypeEnum;
import cn.iocoder.yudao.module.api.promotion.activity.enums.PromotionProductScopeEnum;
import cn.iocoder.yudao.module.api.promotion.activity.enums.PromotionTypeEnum;
import cn.iocoder.yudao.module.api.promotion.coupon.CouponRecordApi;
import cn.iocoder.yudao.module.api.promotion.coupon.dto.CouponRespDTO;
import cn.iocoder.yudao.module.api.promotion.coupon.enums.CouponStatusEnum;
import cn.iocoder.yudao.module.api.promotion.gift.GiftActivityApi;
import cn.iocoder.yudao.module.api.promotion.gift.GiftScanConfigApi;
import cn.iocoder.yudao.module.api.promotion.gift.dto.GiftActivityVO;
import cn.iocoder.yudao.module.api.promotion.newcustomer.NewCustomerApi;
import cn.iocoder.yudao.module.api.promotion.newcustomer.vo.NewCustomerConfigVO;
import cn.iocoder.yudao.module.api.promotion.redpacket.RedPacketApi;
import cn.iocoder.yudao.module.api.promotion.redpacket.enums.RedPacketDiscountModeEnum;
import cn.iocoder.yudao.module.api.promotion.redpacket.vo.RedPacketUsageVO;
import cn.iocoder.yudao.module.api.shop.info.ShopInfoApi;
import cn.iocoder.yudao.module.api.shop.info.dto.ShopInfoDTO;
import cn.iocoder.yudao.module.api.trade.scan.dto.ScanOrderDTO;
import cn.iocoder.yudao.module.trade.scan.dal.dataobject.ScanOrderDO;
import cn.iocoder.yudao.module.trade.scan.dal.mysql.ScanOrderMapper;
import cn.iocoder.yudao.module.trade.scan.mq.ScanOrderProducer;
import cn.iocoder.yudao.module.trade.scan.vo.*;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.module.api.infrastructure.ConfigConstants.CONFIG_MEMBER;
import static cn.iocoder.yudao.module.api.trade.ConfigConstants.SCAN_ORDER_NO_PREFIX;

/**
 * 线下扫码订单 Service 实现类
 *
 * @author 山野羡民
 */
@Slf4j
@Service
@Validated
public class AppScanOrderServiceImpl extends AppOrderBaseService<ScanOrderDO, AppScanOrderVO, ScanOrderSubmitDTO> implements AppScanOrderService {
    @Resource
    private ScanOrderMapper scanOrderMapper;

    @Resource
    private SerialNoRedisRepository serialNoRedisRepository;
    @Resource
    private ScanOrderProducer scanOrderProducer;

    @Resource
    private PayOrderApi payOrderApi;
    @Resource
    private PayBillApi payBillApi;
    @Resource
    private MemberPointApi memberPointApi;
    @Resource
    private MemberDigitalAssetsApi memberDigitalAssetsApi;
    @Resource
    private ShopInfoApi shopInfoApi;
    @Resource
    private MemberUserApi memberUserApi;
    @Resource
    private CouponRecordApi couponRecordApi;
    @Resource
    private VipUserApi vipUserApi;
    @Resource
    private ConfigApiV2 configApiV2;
    @Resource
    private GiftActivityApi giftActivityApi;
    @Resource
    private GiftScanConfigApi giftScanConfigApi;
    @Resource
    private RedPacketApi redPacketApi;
    @Resource
    private NewCustomerApi newCustomerApi;

    @Override
    protected ScanOrderMapper getInjectedMapper() {
        return scanOrderMapper;
    }

    @Override
    public NewCustomerCheckVO checkCustomer(Long shopId, Long userId) {
        NewCustomerCheckVO checkVO = new NewCustomerCheckVO();
        checkVO.setLogin(userId != null && userId > 0);
        NewCustomerConfigVO configVO = newCustomerApi.getNewCustomerConfig(shopId);
        checkVO.setShopId(configVO.getShopId());
        checkVO.setEnabled(configVO.getEnabled());
        checkVO.setOriginPrice(configVO.getOriginPrice());
        checkVO.setPrice(configVO.getPrice());
        checkVO.setRewardType(configVO.getRewardType());
        checkVO.setRedPacketAmount(configVO.getRedPacketAmount());
        boolean newShopCustomer = isNewShopCustomer(shopId, userId);
        checkVO.setMatched(newShopCustomer);
        checkVO.setRedPacketConfig(redPacketApi.getRedPacketConfig(shopId));
        List<GiftActivityVO> giftActivities = List.of();
        if (newShopCustomer) {
            if (CollUtil.isNotEmpty(configVO.getGiftActivityIds())) {
                giftActivities = giftActivityApi.getList(configVO.getGiftActivityIds());
            }
        } else {
            giftActivities = giftScanConfigApi.getGiftActivities(userId, shopId, configVO.getPrice());
        }
        if (CollUtil.isNotEmpty(giftActivities)) {
            checkVO.setGiftActivities(giftActivities);
            checkVO.setGiftActivityIds(giftActivities.stream().map(GiftActivityVO::getId).toList());
            checkVO.setGiftActivityNames(giftActivities.stream().map(GiftActivityVO::getName).toList());
        }
        return checkVO;
    }

    @Override
    public ScanOrderPreviewVO preview(ScanOrderSubmitDTO submitDTO, Long userId) {
        ShopInfoDTO shopInfoDTO = shopInfoApi.validShopInfo(submitDTO.getShopId());
        double discountPercent = shopInfoDTO.getDiscountPercent();
        log.debug("[preview][线下扫码订单价格计算前置条件：平台服务费比例={}%，顾客抵扣比例={}%]", shopInfoDTO.getServiceFeePercent(), discountPercent);
        ScanOrderPreviewVO respVO = new ScanOrderPreviewVO();
        respVO.setPromotions(new ArrayList<>());
        respVO.setShopId(shopInfoDTO.getId());
        respVO.setShop(shopInfoDTO);
        UserSimpleVO user = memberUserApi.getUser(shopInfoDTO.getUserId());
        if (user != null) {
            respVO.setInviteCode(user.getNo());
        }
        if (submitDTO.getPrice() == null || submitDTO.getPrice() <= 0) {
            // 输入的价格无效，不做相关处理
            return respVO;
        }
        // 价格体系：原价、会员价、抵扣价三选一，其中抵扣价的积分抵扣、收益豆全额抵扣、收益豆部分抵扣三选一。
        // 原价：对标市面上产品正常销售的价格，是没有任何优惠的情况下的销售价。
        // 支付价：优先顺序依次为积分抵扣、收益豆全额抵扣、收益豆部分抵扣、会员价、原价。
        // 成本价：原价-原价×平台服务费比例，用于分账给卖家。如设置原价100元、平台抽佣10%后成本价100-100x0.1=90元。
        int originPrice = submitDTO.getPrice();
        respVO.setOriginPrice(originPrice);
        respVO.setSettlementPrice(originPrice);
        List<CouponRespDTO> couponList = couponRecordApi.getCouponListByUserId(userId, CouponStatusEnum.UNUSED.getStatus());
        if (couponList == null) {
            couponList = List.of();
        } else {
            couponList = couponList.stream()
                    // 扫码下单支付这里只能使用通用的优惠券
                    .filter(coupon -> Objects.equals(coupon.getProductScope(), PromotionProductScopeEnum.ALL.getScope()))
                    // 扫码下单待支付金额大于等于优惠券满减门槛金额
                    .filter(coupon -> originPrice >= coupon.getUsePrice())
                    .toList();
        }
        respVO.setCoupons(couponList);
        calculateCouponPrice(respVO, userId, submitDTO.getCouponId(), couponList);
        MemberConfigRespDTO memberConfigRespDTO = configApiV2.getConfig(CONFIG_MEMBER, MemberConfigRespDTO.class);
        respVO.setPointDeductUnitPrice(memberConfigRespDTO.getPointTradeDeductUnitPrice());
        respVO.setCryptocurrencyDeductUnitPrice(memberConfigRespDTO.getCryptocurrencyTradeDeductUnitPrice());
        RedPacketUsageVO redPacketUsageVO = redPacketApi.getMatchedRedPacket(userId, shopInfoDTO.getId(), originPrice);
        respVO.setRedpacket(redPacketUsageVO);
        calculateRedPacketPrice(respVO, userId, redPacketUsageVO);
        calculatePointPrice(respVO, userId, originPrice, discountPercent, memberConfigRespDTO.isPointDeductDisable());
        calculateCryptocurrencyPrice(respVO, userId, originPrice, discountPercent, memberConfigRespDTO.isCryptocurrencyDeductDisable());
        calculateVipPrice(respVO, userId, originPrice, discountPercent);
        respVO.setPayPrice(originPrice);
        respVO.setPointDeductPrice(0);
        respVO.setCryptocurrencyDeductPrice(0);
        respVO.setDeductPoint(0);
        respVO.setDeductCryptocurrency(0d);
        respVO.setVipExemptPrice(0);
        respVO.setRedpacketDeductPrice(0);
        respVO.setDiscountPrice(0);
        boolean isUseCoupon = Boolean.TRUE.equals(submitDTO.getUseCouponDeduction());
        boolean isUsePointDeduction = Boolean.TRUE.equals(submitDTO.getUsePointDeduction());
        boolean isUseCryptocurrencyDeduction = Boolean.TRUE.equals(submitDTO.getUseCryptocurrencyDeduction());
        boolean isUseVipPrice = Boolean.TRUE.equals(submitDTO.getUseVipPrice());
        boolean isUseRedPacketDeduction = Boolean.TRUE.equals(submitDTO.getUseRedPacketDeduction());
        String tips = "未享受优惠";
        for (ScanOrderPreviewVO.ScanPromotion promotion : respVO.getPromotions()) {
            // 优惠券、积分抵扣、收益豆抵扣、会员价、红包抵扣五选一
            if (isUseCoupon && promotion.getType() == PromotionTypeEnum.COUPON.getValue()) {
                tips = promotion.getNote();
                if (promotion.isMatch()) {
                    respVO.setDiscountPrice(promotion.getPrice());
                    respVO.setPayPrice(originPrice - promotion.getPrice());
                }
                break;
            }
            if (isUsePointDeduction && promotion.getType() == PromotionTypeEnum.POINT.getValue()) {
                tips = promotion.getNote();
                if (promotion.isMatch()) {
                    respVO.setPointDeductPrice(promotion.getPrice());
                    respVO.setDeductPoint(promotion.getPrice() / respVO.getPointDeductUnitPrice());
                    respVO.setPayPrice(originPrice - promotion.getPrice());
                }
                break;
            }
            if (isUseCryptocurrencyDeduction && promotion.getType() == PromotionTypeEnum.CRYPTOCURRENCY.getValue()) {
                tips = promotion.getNote();
                if (promotion.isMatch()) {
                    respVO.setCryptocurrencyDeductPrice(promotion.getPrice());
                    respVO.setDeductCryptocurrency(1d * promotion.getPrice() / respVO.getCryptocurrencyDeductUnitPrice());
                    respVO.setPayPrice(originPrice - promotion.getPrice());
                }
                break;
            }
            if (isUseVipPrice && promotion.getType() == PromotionTypeEnum.VIP.getValue()) {
                tips = promotion.getNote();
                if (promotion.isMatch()) {
                    respVO.setVipExemptPrice(promotion.getPrice());
                    respVO.setPayPrice(originPrice - promotion.getPrice());
                }
                break;
            }
            if (isUseRedPacketDeduction && promotion.getType() == PromotionTypeEnum.RED_PACKET.getValue()) {
                tips = promotion.getNote();
                if (promotion.isMatch()) {
                    respVO.setRedpacketDeductPrice(promotion.getPrice());
                    respVO.setPayPrice(originPrice - promotion.getPrice());
                }
                break;
            }
        }
        respVO.setTips(tips);
        // 不以订单金额来计算赠品满送条件，而是以实际支付金额来计算赠品满送条件
        List<GiftActivityVO> giftActivities = giftScanConfigApi.getGiftActivities(userId, shopInfoDTO.getId(), respVO.getPayPrice());
        respVO.setGiftActivities(giftActivities);
        return respVO;
    }

    private void calculateCouponPrice(@Nonnull ScanOrderPreviewVO respVO, @Nullable Long userId, @Nullable Long couponId, @Nonnull List<CouponRespDTO> couponList) {
        boolean notLogin = userId == null || userId <= 0;
        ScanOrderPreviewVO.ScanPromotion promotion = new ScanOrderPreviewVO.ScanPromotion();
        promotion.setType(PromotionTypeEnum.COUPON.getValue());
        promotion.setName(PromotionTypeEnum.COUPON.getName());
        if (notLogin) {
            promotion.setPrice(0);
            promotion.setMatch(false);
            promotion.setNote("登录后享优惠券抵扣");
            respVO.getPromotions().add(promotion);
            return;
        }
        promotion.setName(PromotionTypeEnum.COUPON.getName() + "(" + couponList.size() + "张)");
        if (CollUtil.isEmpty(couponList)) {
            promotion.setPrice(0);
            promotion.setMatch(false);
            promotion.setNote("无匹配优惠券");
            respVO.getPromotions().add(promotion);
            return;
        }
        if (couponId == null || couponId <= 0) {
            promotion.setPrice(0);
            promotion.setMatch(false);
            promotion.setNote("请选择优惠券");
            respVO.getPromotions().add(promotion);
            return;
        }
        CouponRespDTO couponRespDTO = null;
        for (CouponRespDTO coupon : couponList) {
            if (Objects.equals(coupon.getId(), couponId)) {
                couponRespDTO = coupon;
                break;
            }
        }
        if (couponRespDTO == null) {
            promotion.setPrice(0);
            promotion.setMatch(false);
            promotion.setNote("所选优惠券无效");
            respVO.getPromotions().add(promotion);
            return;
        }
        respVO.setCoupon(couponRespDTO);
        int canDiscountPrice;
        if (Objects.equals(couponRespDTO.getDiscountType(), PromotionDiscountTypeEnum.PRICE.getType())) {
            // 满减
            canDiscountPrice = couponRespDTO.getDiscountPrice();
        } else {
            // 折扣
            double discount = couponRespDTO.getDiscountPercent() / 100d;
            canDiscountPrice = (int) (respVO.getOriginPrice() * discount);
            canDiscountPrice = Math.min(canDiscountPrice, couponRespDTO.getDiscountLimitPrice());
        }
        int discountPrice = Math.min(canDiscountPrice, respVO.getOriginPrice());
        promotion.setPrice(discountPrice);
        promotion.setMatch(true);
        promotion.setNote("优惠券抵扣 " + MoneyUtils.fenToYuanStr(discountPrice) + "元");
        respVO.getPromotions().add(promotion);
    }

    private void calculateRedPacketPrice(ScanOrderPreviewVO respVO, Long userId, RedPacketUsageVO redPacketUsageVO) {
        boolean notLogin = userId == null || userId <= 0;
        ScanOrderPreviewVO.ScanPromotion promotion = new ScanOrderPreviewVO.ScanPromotion();
        promotion.setType(PromotionTypeEnum.RED_PACKET.getValue());
        if (notLogin) {
            promotion.setName(PromotionTypeEnum.RED_PACKET.getName());
        } else {
            promotion.setName(PromotionTypeEnum.RED_PACKET.getName() + "(" + MoneyUtils.fenToYuanStr(redPacketUsageVO.getUnusedRedPacketAmount()) + "元)");
        }
        if (redPacketUsageVO.getCanDiscountPrice() <= 0) {
            promotion.setPrice(0);
            promotion.setMatch(false);
            promotion.setNote(redPacketUsageVO.getCannotDiscountReason());
            respVO.getPromotions().add(promotion);
            return;
        }
        if (notLogin) {
            promotion.setMatch(false);
            promotion.setPrice(redPacketUsageVO.getCanDiscountPrice());
            promotion.setNote("新客红包抵扣 " + MoneyUtils.fenToYuanStr(redPacketUsageVO.getCanDiscountPrice()) + "元");
            respVO.getPromotions().add(promotion);
            return;
        }
        if (redPacketUsageVO.getUnusedRedPacketAmount() <= 0) {
            promotion.setMatch(false);
            promotion.setPrice(redPacketUsageVO.getCanDiscountPrice());
            promotion.setNote("没有可用的红包");
            respVO.getPromotions().add(promotion);
            return;
        }
        if (redPacketUsageVO.getUnusedRedPacketAmount() >= redPacketUsageVO.getCanDiscountPrice()) {
            promotion.setMatch(true);
            promotion.setPrice(redPacketUsageVO.getCanDiscountPrice());
            promotion.setNote("红包抵扣 " + MoneyUtils.fenToYuanStr(redPacketUsageVO.getCanDiscountPrice()) + "元");
            respVO.getPromotions().add(promotion);
            return;
        }
        if (Objects.equals(redPacketUsageVO.getDiscountMode(), RedPacketDiscountModeEnum.FIXED.getValue())) {
            // 抵扣固定金额
            promotion.setMatch(false);
            promotion.setPrice(redPacketUsageVO.getUnusedRedPacketAmount());
            promotion.setNote("红包不足，还差" + MoneyUtils.fenToYuanStr(redPacketUsageVO.getCanDiscountPrice() - redPacketUsageVO.getUnusedRedPacketAmount()) + "元");
            respVO.getPromotions().add(promotion);
            return;
        }
        // 自由抵扣，有多少抵扣多少
        int discountPrice = Math.min(redPacketUsageVO.getUnusedRedPacketAmount(), respVO.getOriginPrice());
        promotion.setMatch(true);
        promotion.setPrice(discountPrice);
        promotion.setNote("红包抵扣 " + MoneyUtils.fenToYuanStr(discountPrice) + "元");
        respVO.getPromotions().add(promotion);
    }

    private void calculatePointPrice(ScanOrderPreviewVO respVO, Long userId, int originPrice, double shopDiscountPercent, boolean isPointDeductDisable) {
        boolean notLogin = userId == null || userId <= 0;
        ScanOrderPreviewVO.ScanPromotion promotion = new ScanOrderPreviewVO.ScanPromotion();
        promotion.setType(PromotionTypeEnum.POINT.getValue());
        promotion.setName(PromotionTypeEnum.POINT.getName());
        if (notLogin) {
            promotion.setPrice(0);
            promotion.setMatch(false);
            promotion.setNote("登录后享积分抵扣");
            respVO.getPromotions().add(promotion);
            return;
        }
        int userPoint = memberDigitalAssetsApi.getDetail(userId).getPoint();
        promotion.setName(PromotionTypeEnum.POINT.getName() + "(" + userPoint + "积分)");
        if (isPointDeductDisable) {
            promotion.setPrice(0);
            promotion.setMatch(false);
            promotion.setNote("积分抵扣功能未开启");
            respVO.getPromotions().add(promotion);
            return;
        }
        // 积分抵扣价：所需积分=原价×顾客抵扣比例×积分现金价值。如设置原价100元、顾客抵扣10%，1个积分现金价值1元，则10积分抵扣10元，积分抵扣价为90元+10积分。
        int pointDeductPrice = MoneyUtils.calculateRatePriceFloor(originPrice, shopDiscountPercent);
        int deductPoint = (int) (pointDeductPrice * 1D / respVO.getPointDeductUnitPrice());
        // 积分是整数，需忽略无法抵扣的小数部分
        pointDeductPrice = deductPoint * respVO.getPointDeductUnitPrice();
        if (deductPoint > 0) {
            int diffPoint = userPoint - deductPoint;
            if (diffPoint >= 0) {
                promotion.setPrice(pointDeductPrice);
                promotion.setMatch(true);
                promotion.setNote(deductPoint + "积分抵扣" + MoneyUtils.fenToYuanStr(pointDeductPrice) + "元");
                respVO.getPromotions().add(promotion);
                return;
            }
            promotion.setPrice(pointDeductPrice);
            promotion.setMatch(false);
            promotion.setNote("积分不足，还差" + Math.abs(diffPoint) + "个");
            respVO.getPromotions().add(promotion);
            return;
        }
        promotion.setPrice(0);
        promotion.setMatch(false);
        promotion.setNote("不支持积分抵扣");
        respVO.getPromotions().add(promotion);
    }

    private void calculateCryptocurrencyPrice(ScanOrderPreviewVO respVO, Long userId, int originPrice, double shopDiscountPercent, boolean isCryptocurrencyDeductDisable) {
        boolean notLogin = userId == null || userId <= 0;
        ScanOrderPreviewVO.ScanPromotion promotion = new ScanOrderPreviewVO.ScanPromotion();
        promotion.setType(PromotionTypeEnum.CRYPTOCURRENCY.getValue());
        promotion.setName(PromotionTypeEnum.CRYPTOCURRENCY.getName());
        if (notLogin) {
            promotion.setPrice(0);
            promotion.setMatch(false);
            promotion.setNote("登录后享收益豆抵扣");
            respVO.getPromotions().add(promotion);
            return;
        }
        int userCryptocurrency = memberDigitalAssetsApi.getDetail(userId).getCryptocurrency().intValue();
        promotion.setName(PromotionTypeEnum.CRYPTOCURRENCY.getName() + "(" + userCryptocurrency + "豆)");
        if (isCryptocurrencyDeductDisable) {
            promotion.setPrice(0);
            promotion.setMatch(false);
            promotion.setNote("收益豆抵扣功能未开启");
            respVO.getPromotions().add(promotion);
            return;
        }
        // 收益豆部分抵扣价：所需收益豆=原价×顾客抵扣比例×收益豆现金价值。如设置原价100元、顾客抵扣30%，折扣大于5折，1个收益豆现金价值1元，则30个收益豆抵扣30元，收益豆抵扣价为70元+30收益豆。用户收益豆不足则用现金补充。
        int maxCryptocurrencyDeductPrice = MoneyUtils.calculateRatePriceFloor(originPrice, shopDiscountPercent);
        if (maxCryptocurrencyDeductPrice <= 0) {
            promotion.setPrice(0);
            promotion.setMatch(false);
            promotion.setNote("不支持收益豆抵扣");
            respVO.getPromotions().add(promotion);
            return;
        }
        // 收益豆全额抵扣价：所需收益豆=原价×收益豆现金价值。如设置原价100元、成本价50元，折扣等于5折，1个收益豆现金价值1元，则100收益豆抵扣100元，收益豆抵扣价为100收益豆。
        int discount = (int) ((1D * (originPrice - maxCryptocurrencyDeductPrice) / originPrice) * 10);
        boolean fullDeduction = discount <= 5;
        int deductCryptocurrency;
        int cryptocurrencyDeductPrice;
        if (fullDeduction) {
            // 全额抵扣
            deductCryptocurrency = (int) (originPrice * 1D / respVO.getCryptocurrencyDeductUnitPrice());
            cryptocurrencyDeductPrice = originPrice;
        } else {
            double maxDeductCryptocurrency = (maxCryptocurrencyDeductPrice * 1D / respVO.getCryptocurrencyDeductUnitPrice());
            deductCryptocurrency = (int) maxDeductCryptocurrency;
            if (userCryptocurrency > 0) {
                deductCryptocurrency = (int) Math.min(maxDeductCryptocurrency, userCryptocurrency);
            }
            // 收益豆是整数，需忽略无法抵扣的小数部分
            cryptocurrencyDeductPrice = deductCryptocurrency * respVO.getCryptocurrencyDeductUnitPrice();
        }
        if (fullDeduction) {
            double diffCryptocurrency = userCryptocurrency - deductCryptocurrency;
            if (diffCryptocurrency >= 0) {
                promotion.setPrice(cryptocurrencyDeductPrice);
                promotion.setMatch(true);
                promotion.setNote(deductCryptocurrency + "个收益豆全额兑换");
                respVO.getPromotions().add(promotion);
                return;
            }
            promotion.setPrice(cryptocurrencyDeductPrice);
            promotion.setMatch(false);
            promotion.setNote("收益豆不足，还差" + Math.abs(diffCryptocurrency) + "个");
            respVO.getPromotions().add(promotion);
            return;
        }
        if (deductCryptocurrency > 0) {
            double diffCryptocurrency = userCryptocurrency - deductCryptocurrency;
            if (diffCryptocurrency >= 0) {
                promotion.setPrice(cryptocurrencyDeductPrice);
                promotion.setMatch(true);
                promotion.setNote(deductCryptocurrency + "收益豆抵扣" + MoneyUtils.fenToYuanStr(cryptocurrencyDeductPrice) + "元");
                respVO.getPromotions().add(promotion);
                return;
            }
            promotion.setPrice(cryptocurrencyDeductPrice);
            promotion.setMatch(false);
            promotion.setNote("收益豆不足，还差" + Math.abs((int) diffCryptocurrency) + "个");
            respVO.getPromotions().add(promotion);
            return;
        }
        promotion.setPrice(0);
        promotion.setMatch(false);
        promotion.setNote("不支持收益豆抵扣");
        respVO.getPromotions().add(promotion);
    }

    private void calculateVipPrice(ScanOrderPreviewVO respVO, Long userId, int originPrice, double shopDiscountPercent) {
        boolean notLogin = userId == null || userId <= 0;
        ScanOrderPreviewVO.ScanPromotion promotion = new ScanOrderPreviewVO.ScanPromotion();
        promotion.setType(PromotionTypeEnum.VIP.getValue());
        promotion.setName(PromotionTypeEnum.VIP.getName());
        if (notLogin) {
            promotion.setPrice(0);
            promotion.setMatch(false);
            promotion.setNote("登录后享会员价");
            respVO.getPromotions().add(promotion);
            return;
        }
        // 会员价：介于成本价或抵扣价中最大者和原价之间，单个产品商家可自行调整，会员价比抵扣价大的目的是促使用户优先抵扣。
        // 如设置原价100元、成本价90元、抵扣价90元+10积分、抵扣价70元+30收益豆、会员价95元。
        int vipExemptedPrice = MoneyUtils.calculateRatePriceFloor(originPrice, shopDiscountPercent);
        promotion.setPrice(vipExemptedPrice);
        if (vipExemptedPrice <= 0) {
            promotion.setMatch(false);
            promotion.setNote("不支持会员价");
            respVO.getPromotions().add(promotion);
            return;
        }
        if (vipUserApi.validVip(userId, null)) {
            promotion.setMatch(true);
            promotion.setNote("会员价 ¥%s".formatted(MoneyUtils.fenToYuanStr(originPrice - vipExemptedPrice)));
            respVO.getPromotions().add(promotion);
            return;
        }
        promotion.setMatch(false);
        promotion.setNote("开通会员后立减 ¥%s".formatted(MoneyUtils.fenToYuanStr(vipExemptedPrice)));
        respVO.getPromotions().add(promotion);
    }

    @Nonnull
    @Override
    protected ScanOrderDO onOrderCreateBefore(@Nonnull ScanOrderSubmitDTO submitVO, @Nonnull Long userId) {
        ScanOrderPreviewVO preview = preview(submitVO, userId);
        if (Boolean.TRUE.equals(submitVO.getUsePointDeduction())) {
            Assertions.isTrue(preview.getDeductPoint() > 0, preview.getTips());
        }
        if (Boolean.TRUE.equals(submitVO.getUseCryptocurrencyDeduction())) {
            Assertions.isTrue(preview.getDeductCryptocurrency() > 0, preview.getTips());
        }
        if (Boolean.TRUE.equals(submitVO.getUseVipPrice())) {
            Assertions.isTrue(preview.getVipExemptPrice() > 0, preview.getTips());
        }
        if (Boolean.TRUE.equals(submitVO.getUseRedPacketDeduction())) {
            Assertions.isTrue(preview.getRedpacketDeductPrice() > 0, preview.getTips());
        }
        if (Boolean.TRUE.equals(submitVO.getUseCouponDeduction())) {
            Assertions.isTrue(preview.getDiscountPrice() > 0, preview.getTips());
        }
        // 构建订单信息
        String note = "扫码付款给商家【%s】，%s".formatted(preview.getShop().getName(), preview.getTips());
        ScanOrderDO order = new ScanOrderDO();
        order.setUserId(userId);
        order.setShopId(preview.getShopId());
        order.setOriginPrice(preview.getOriginPrice());
        order.setSettlementPrice(preview.getSettlementPrice());
        order.setPrice(preview.getPayPrice());
        order.setPointDeductPrice(preview.getPointDeductPrice());
        order.setCryptocurrencyDeductPrice(preview.getCryptocurrencyDeductPrice());
        order.setDeductPoint(preview.getDeductPoint());
        order.setDeductCryptocurrency(preview.getDeductCryptocurrency());
        order.setVipExemptPrice(preview.getVipExemptPrice());
        order.setRedpacketDeductPrice(preview.getRedpacketDeductPrice());
        order.setDiscountPrice(preview.getDiscountPrice());
        CouponRespDTO coupon = preview.getCoupon();
        if (coupon != null) {
            order.setCouponId(coupon.getId());
        } else {
            order.setCouponId(0L);
        }
        OrderSnapshotVO snapshotVO = new OrderSnapshotVO();
        snapshotVO.setName(note);
        snapshotVO.setPicUrl("");
        snapshotVO.setCount(1);
        snapshotVO.setOriginPrice(preview.getOriginPrice());
        snapshotVO.setPayPrice(preview.getPayPrice());
        order.setSnapshot(JSONUtil.toJsonStr(snapshotVO));
        order.setTradeNo(serialNoRedisRepository.generateNo(SCAN_ORDER_NO_PREFIX));
        order.setUserRemark(submitVO.getRemark());
        order.setNote(note);
        order.setGiftActivitySnapshot(JsonUtils.toJsonString(preview.getGiftActivities()));
        order.setGiftActivityIds(preview.getGiftActivityIds());
        return order;
    }

    @Async
    @Override
    public void createPayOrder(@Nonnull ScanOrderDO order) {
        PayOrderCreateDTO createDTO = new PayOrderCreateDTO();
        createDTO.setTradeNo(order.getTradeNo());
        createDTO.setSubject(BillTypeEnum.PAY_OFFLINE_SCAN.getName());
        createDTO.setBody(order.getNote());
        createDTO.setPrice(order.getPrice());
        String payNo = payOrderApi.createOrder(createDTO);
        // 创建付款单据后再回来更新业务订单的支付单号
        ScanOrderDO updateObj = new ScanOrderDO();
        updateObj.setId(order.getId());
        updateObj.setPayNo(payNo);
        getInjectedMapper().updateById(updateObj);
    }

    @Override
    protected void onPaidAfter(ScanOrderDO order) {
        ScanOrderDTO scanOrderDTO = BeanUtils.toBean(order, ScanOrderDTO.class);
        scanOrderDTO.setShop(shopInfoApi.getShopInfo(order.getShopId()));
        scanOrderProducer.sendScanOrderPaidMessage(scanOrderDTO);
        // 扣除用户资产 TODO 后续重构改为事件监听的方式进行解耦
        reduceMemberDigitalAssetsAsync(order);
        // 释放商家联盟豆 TODO 后续重构改为事件监听的方式进行解耦
        releaseMerchantCryptocurrencyAsync(order);
    }

    @Async
    public void reduceMemberDigitalAssetsAsync(ScanOrderDO order) {
        // 扣除积分
        if (order.getDeductPoint() != null && order.getDeductPoint() > 0) {
            memberPointApi.reducePoint(order.getUserId(), order.getDeductPoint(), MemberPointBizTypeEnum.OFFLINE_SCAN_ORDER_DEDUCT.getType(), order.getTradeNo());
        }
        // 扣除并转移收益豆
        else if (order.getDeductCryptocurrency() != null && order.getDeductCryptocurrency() > 0) {
            Long buyerUserId = order.getUserId();
            Long sellerUserId = shopInfoApi.getOwnerMemberUserId(order.getShopId());
            log.debug("[onOrderRefund] 订单【{}】支付，买家用户编号【{}】，卖家店铺编号【{}】，卖家用户编号【{}】", order.getTradeNo(), buyerUserId, order.getShopId(), sellerUserId);
            CryptocurrencyDeductionType deductionType = memberDigitalAssetsApi.payCryptocurrencyIncrement(buyerUserId, order.getDeductCryptocurrency());
            if (deductionType == CryptocurrencyDeductionType.NONE) {
                return;
            }
            PayBillCreateDTO buyerCreateDTO = new PayBillCreateDTO();
            buyerCreateDTO.setUserId(buyerUserId);
            if (deductionType == CryptocurrencyDeductionType.INCREMENT) {
                buyerCreateDTO.setCategory(BillCategoryEnum.CRYPTOCURRENCY_INCREMENT);
                buyerCreateDTO.setMark("订单【%s】支付，使用【%s个】收益豆抵现".formatted(order.getTradeNo(), order.getDeductCryptocurrency()));
            } else {
                buyerCreateDTO.setCategory(BillCategoryEnum.CRYPTOCURRENCY_ORIGIN);
                buyerCreateDTO.setMark("订单【%s】支付，使用【%s个】联盟豆抵现".formatted(order.getTradeNo(), order.getDeductCryptocurrency()));
            }
            buyerCreateDTO.setType(BillTypeEnum.PAY_OFFLINE_SCAN);
            buyerCreateDTO.setNumber(-1 * order.getDeductCryptocurrency());
            buyerCreateDTO.setRelateId(order.getTradeNo());
            buyerCreateDTO.setExtendField("{\"scanOrderId\":\"%s\"}".formatted(order.getId()));
            payBillApi.expendAsync(buyerCreateDTO);
            memberDigitalAssetsApi.exchangeCryptocurrencyOriginToIncrement(sellerUserId, order.getDeductCryptocurrency());
            PayBillCreateDTO sellerCreateDTO = new PayBillCreateDTO();
            sellerCreateDTO.setUserId(sellerUserId);
            sellerCreateDTO.setCategory(BillCategoryEnum.CRYPTOCURRENCY_INCREMENT);
            sellerCreateDTO.setType(BillTypeEnum.PAY_OFFLINE_SCAN);
            sellerCreateDTO.setNumber(order.getDeductCryptocurrency());
            sellerCreateDTO.setMark("订单【%s】支付，获得买家抵扣的收益豆【%s个】".formatted(order.getTradeNo(), order.getDeductCryptocurrency()));
            sellerCreateDTO.setRelateId(order.getTradeNo());
            sellerCreateDTO.setExtendField("{\"scanOrderId\":\"%s\"}".formatted(order.getId()));
            payBillApi.incomeAsync(sellerCreateDTO);
        }
    }

    @Async
    public void releaseMerchantCryptocurrencyAsync(ScanOrderDO order) {
        ShopInfoDTO shopInfoRespDTO = shopInfoApi.getShopInfo(order.getShopId());
        Assertions.notNull(shopInfoRespDTO, "未获取到商家信息：shopId=" + order.getShopId());
        double cryptocurrency = MoneyUtils.fenToYuan((int) (order.getPrice() * shopInfoRespDTO.getServiceFeePercent() / 100f)).doubleValue();
        MemberDigitalAssetsRespDTO digitalAssetsRespDTO = memberDigitalAssetsApi.getDetail(shopInfoRespDTO.getUserId());
        if (digitalAssetsRespDTO.getCryptocurrencyFreezing().doubleValue() > cryptocurrency) {
            memberDigitalAssetsApi.unfreezeCryptocurrencyOrigin(shopInfoRespDTO.getUserId(), cryptocurrency);
            PayBillCreateDTO createDTO = new PayBillCreateDTO();
            createDTO.setUserId(shopInfoRespDTO.getUserId());
            createDTO.setCategory(BillCategoryEnum.CRYPTOCURRENCY_ORIGIN);
            createDTO.setType(BillTypeEnum.PAY_OFFLINE_SCAN);
            createDTO.setNumber(cryptocurrency);
            createDTO.setMark("商家【%s】收款码入账【%s元】，释放【%s个】联盟豆".formatted(shopInfoRespDTO.getName(), MoneyUtils.fenToYuanStr(order.getPrice()), cryptocurrency));
            createDTO.setRelateId(order.getTradeNo());
            createDTO.setExtendField("{\"scanOrderId\":\"%s\",\"percent\":%f}".formatted(order.getId(), shopInfoRespDTO.getServiceFeePercent()));
            payBillApi.incomeAsync(createDTO);
        } else {
            log.warn("商家({})用户({})收款({})，冻结中的联盟豆余额不足，忽略联盟豆释放", shopInfoRespDTO.getName(), shopInfoRespDTO.getUserId(), order.getTradeNo());
        }
    }

    @Override
    public PageResult<AppScanOrderVO> getPage(Integer pageNo, Integer pageSize, Long userId) {
        CoreQueryWrapper<ScanOrderDO> wrapper = new CoreQueryWrapper<>();
        wrapper.eq("user_id", userId);
        wrapper.orderByDesc("id");
        PageResult<ScanOrderDO> page = scanOrderMapper.selectPage(pageNo, pageSize, wrapper);
        if (CollUtil.isEmpty(page.getList())) {
            return PageResult.empty();
        }
        PageResult<AppScanOrderVO> result = BeanUtils.toBean(page, AppScanOrderVO.class);
        Set<Long> shopIds = page.getList().stream().map(ScanOrderDO::getShopId).collect(Collectors.toSet());
        Map<Long, ShopInfoDTO> shops = shopInfoApi.getShopInfoMap(shopIds);
        result.getList().forEach(order -> {
            ShopInfoDTO shop = shops.getOrDefault(order.getShopId(), ShopInfoDTO.FALLBACK);
            order.setShop(shop);
        });
        return result;
    }

    @Override
    public PageResult<AppScanOrderVO> getPageForShop(Integer pageNo, Integer pageSize, Set<Long> shopIds) {
        if (CollUtil.isEmpty(shopIds)) {
            return PageResult.empty();
        }
        CoreQueryWrapper<ScanOrderDO> wrapper = new CoreQueryWrapper<>();
        wrapper.inIfPresent("shop_id", shopIds);
        wrapper.orderByDesc("id");
        PageResult<ScanOrderDO> page = scanOrderMapper.selectPage(pageNo, pageSize, wrapper);
        if (CollUtil.isEmpty(page.getList())) {
            return PageResult.empty();
        }
        return BeanUtils.toBean(page, AppScanOrderVO.class);
    }

    @Override
    public boolean isNewShopCustomer(Long shopId, Long userId) {
        if (userId == null || userId <= 0) {
            return false;
        }
        return scanOrderMapper.selectCountByShopIdAndUserId(shopId, userId) == 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public OrderSubmitVO submitForNewCustomer(NewCustomerSubmitDTO submitDTO, Long userId) {
        boolean newShopCustomer = isNewShopCustomer(submitDTO.getShopId(), userId);
        if (!newShopCustomer) {
            submitDTO.setPrice(submitDTO.getOriginPrice());
        }
        ShopInfoDTO shopInfoDTO = shopInfoApi.validShopInfo(submitDTO.getShopId());
        long userIdNonnull = userId == null ? 0L : userId;
        ScanOrderDO order = new ScanOrderDO();
        order.setUserId(userIdNonnull);
        order.setShopId(shopInfoDTO.getId());
        order.setOriginPrice(submitDTO.getOriginPrice());
        order.setSettlementPrice(submitDTO.getPrice());
        order.setPrice(submitDTO.getPrice());
        order.setPointDeductPrice(0);
        order.setCryptocurrencyDeductPrice(0);
        order.setDeductPoint(0);
        order.setDeductCryptocurrency(0D);
        order.setVipExemptPrice(0);
        order.setRedpacketDeductPrice(0);
        int discountPrice = submitDTO.getOriginPrice() - submitDTO.getPrice();
        order.setDiscountPrice(discountPrice);
        order.setCouponId(0L);
        String note = "扫码付款给商家【%s】，%s".formatted(shopInfoDTO.getName(), discountPrice > 0 ? "使用首单优惠" : "未使用首单优惠");
        OrderSnapshotVO snapshotVO = new OrderSnapshotVO();
        snapshotVO.setName(note);
        snapshotVO.setPicUrl("");
        snapshotVO.setCount(1);
        snapshotVO.setOriginPrice(submitDTO.getOriginPrice());
        snapshotVO.setPayPrice(submitDTO.getPrice());
        order.setSnapshot(JSONUtil.toJsonStr(snapshotVO));
        order.setTradeNo(serialNoRedisRepository.generateNo(SCAN_ORDER_NO_PREFIX));
        order.setUserRemark(submitDTO.getRemark());
        order.setNote(note);
        List<GiftActivityVO> giftActivities = List.of();
        if (newShopCustomer) {
            NewCustomerConfigVO configVO = newCustomerApi.getNewCustomerConfig(shopInfoDTO.getId());
            if (CollUtil.isNotEmpty(configVO.getGiftActivityIds())) {
                giftActivities = giftActivityApi.getList(configVO.getGiftActivityIds());
            }
        } else {
            giftActivities = giftScanConfigApi.getGiftActivities(userId, shopInfoDTO.getId(), submitDTO.getPrice());
        }
        if (CollUtil.isNotEmpty(giftActivities)) {
            order.setGiftActivityIds(giftActivities.stream().map(GiftActivityVO::getId).toList());
            order.setGiftActivitySnapshot(JsonUtils.toJsonString(giftActivities));
        }
        getInjectedMapper().insert(order);
        createPayOrder(order);
        OrderSubmitVO respVO = new OrderSubmitVO();
        respVO.setTradeNo(order.getTradeNo());
        respVO.setPrice(order.getPrice());
        return respVO;
    }

}